package com.spring.picture.manager;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.internal.Mimetypes;
import com.aliyun.oss.model.*;
import com.spring.picture.config.OSSClientConfig;
import com.spring.picture.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Component
public class OSSManager {

    @Resource
    private OSS ossClient;

    @Resource
    private OSSClientConfig ossClientConfig;

    /**
     * 生成key（OSS中的文件存储路径）
     */
    public String generateKey(MultipartFile file) {
        //获取原生文件名
        String originalFilename = file.getOriginalFilename();
        //JDK8的日期格式
        LocalDateTime time = LocalDateTime.now();
        DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy/MM/dd");

        //拼装OSS上存储的路径
        String folder = dft.format(time);
        String fileName = UUIDUtil.generateUUID();
        String extension = null;
        if (originalFilename != null) {
            extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        }

        //在OSS上bucket下的文件名
        return "user/" + folder + "/" + fileName + extension;
    }


    /**
     * 简单上传
     * @param key 唯一键 （可用文件名）
     * @param file 文件
     */
    public PutObjectResult putObject(String key, MultipartFile file) throws IOException {
        return ossClient.putObject(ossClientConfig.getBucketName(), key, file.getInputStream());
    }

    /**
     * 分片上传
     * @param key 唯一键 （可用文件名）
     * @param file 文件
     */
    public CompleteMultipartUploadResult completeMultipartUpload(String key, MultipartFile file) throws IOException {
        // 创建InitiateMultipartUploadRequest对象。
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(ossClientConfig.getBucketName(), key);

        // 创建ObjectMetadata并设置Content-Type。
        ObjectMetadata metadata = new ObjectMetadata();
        if (metadata.getContentType() == null) {
            metadata.setContentType(Mimetypes.getInstance().getMimetype(key));
        }
        System.out.println("Content-Type: " + metadata.getContentType());

        // 将metadata绑定到上传请求中。
        request.setObjectMetadata(metadata);

        // 初始化分片。
        InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
        // 返回uploadId。
        String uploadId = upresult.getUploadId();

        // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
        List<PartETag> partETags = new ArrayList<>();
        // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
        // 分片最小值为100 KB，最大值为5 GB。最后一个分片的大小允许小于100 KB。
        // 设置分片大小为 1 MB。
        final long partSize = 1 * 1024 * 1024L;

        // 根据上传的数据大小计算分片数。以本地文件为例，说明如何通过getSize获取上传数据的大小。
        long fileLength = file.getSize();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }
        // 遍历分片上传。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(ossClientConfig.getBucketName());
            uploadPartRequest.setKey(key);
            uploadPartRequest.setUploadId(uploadId);
            // 设置上传的分片流。
            // 以本地文件为例说明如何创建FileInputStream，并通过InputStream.skip()方法跳过指定数据。
            InputStream instream = file.getInputStream();
            instream.skip(startPos);
            uploadPartRequest.setInputStream(instream);
            // 设置分片大小。
            uploadPartRequest.setPartSize(curPartSize);
            // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
            uploadPartRequest.setPartNumber(i + 1);
            // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
            partETags.add(uploadPartResult.getPartETag());

            // 关闭流
            instream.close();
        }

        // 创建CompleteMultipartUploadRequest对象。
        // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(ossClientConfig.getBucketName(), key, uploadId, partETags);

        // 完成分片上传。
        CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        System.out.println("上传成功，ETag：" + completeMultipartUploadResult.getETag());
        return completeMultipartUploadResult;
    }

    /**
     * 断点续传
     */
    public UploadFileResult uploadLargeFile(String key, MultipartFile file) throws Throwable {

        ObjectMetadata meta = new ObjectMetadata();
        // 指定上传的内容类型。
        // meta.setContentType("text/plain");
        // 文件上传时设置访问权限ACL。
        // meta.setObjectAcl(CannedAccessControlList.Private);

        // 通过UploadFileRequest设置多个参数。
        // 依次填写Bucket名称（例如examplebucket）以及Object完整路径（例如exampledir/exampleobject.txt），Object完整路径中不能包含Bucket名称。
        UploadFileRequest uploadFileRequest = new UploadFileRequest(ossClientConfig.getBucketName(),key);

        // 通过UploadFileRequest设置单个参数。
        // 填写本地文件的完整路径，例如D:\\localpath\\examplefile.txt。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。
        //uploadFileRequest.setUploadFile("D:\\localpath\\examplefile.txt");
        String filename = file.getOriginalFilename();
        // filepath 一定要是完整路径
        String filepath = System.getProperty("java.io.tmpdir") + filename;
        // 创建一个临时文件
        File file1 = new File(filepath);
        file.transferTo(file1);
        uploadFileRequest.setUploadFile(filepath);
        // 指定上传并发线程数，默认值为1。
        uploadFileRequest.setTaskNum(5);
        // 指定上传的分片大小，单位为字节，取值范围为100 KB~5 GB。默认值为100 KB。
        uploadFileRequest.setPartSize(1 * 1024 * 1024);  // 1MB
        // 开启断点续传，默认关闭。
        uploadFileRequest.setEnableCheckpoint(true);
        // 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传完成后，该文件会被删除。
        // 如果未设置该值，默认与待上传的本地文件同路径，名称为${uploadFile}.ucp。
        //uploadFileRequest.setCheckpointFile("yourCheckpointFile");
        // 文件的元数据。
        uploadFileRequest.setObjectMetadata(meta);
        // 设置上传回调，参数为Callback类型。
        //uploadFileRequest.setCallback("yourCallbackEvent");

        // 断点续传上传。
        UploadFileResult result = ossClient.uploadFile(uploadFileRequest);

        // 删除临时文件
        boolean delete = file1.delete();
        if (!delete) {
            log.error("file delete failed, filePath:{}", filepath);
        }

        return result;
    }

    /**
     * 生成预签名 URL
     */
    public URL generatePresignedUrl(String key) {
        // 指定生成的预签名URL过期时间，单位为毫秒。本示例以设置过期时间为1小时为例。
        Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

        // 生成预签名URL。
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(ossClientConfig.getBucketName(), key, HttpMethod.PUT);
        // 设置过期时间。
        request.setExpiration(expiration);
        // 通过HTTP PUT请求生成预签名URL。
        return ossClient.generatePresignedUrl(request);
    }

    /**
     * 简单下载
     */
    public OSSObject getObject(String key) {
        return ossClient.getObject(ossClientConfig.getBucketName(), key);
    }

    /**
     * 断点续传下载
     * @param key 文件路径
     */
    public ObjectMetadata getObjectMetadata(String key) throws Throwable {
        DownloadFileRequest downloadFileRequest = new DownloadFileRequest(ossClientConfig.getBucketName(), key);
        // 指定Object下载到本地文件的完整路径，例如D:\\localpath\\examplefile.txt。
        downloadFileRequest.setDownloadFile("D:\\localpath\\examplefile.txt");
        // 设置分片大小，单位为字节，取值范围为100 KB~5 GB。默认值为100 KB。
        downloadFileRequest.setPartSize(1024 * 1024);  // 1MB
        // 设置分片下载的并发数，默认值为1。
        downloadFileRequest.setTaskNum(10);
        // 开启断点续传下载，默认关闭。
        downloadFileRequest.setEnableCheckpoint(true);
        // 设置断点记录文件的完整路径，例如D:\\localpath\\examplefile.txt.dcp。
        // 只有当Object下载中断产生了断点记录文件后，如果需要继续下载该Object，才需要设置对应的断点记录文件。下载完成后，该文件会被删除。
        //downloadFileRequest.setCheckpointFile("D:\\localpath\\examplefile.txt.dcp");

        // 下载文件。
        DownloadFileResult downloadRes = ossClient.downloadFile(downloadFileRequest);
        // 下载成功时，会返回文件元数据。
        return downloadRes.getObjectMetadata();
    }
}
